/*
 * @copyright: Huang Ding
 * @Author: ding-cx
 * @Date: 2022-03-28 11:19:29
 * @LastEditors: ding-cx
 * @LastEditTime: 2022-03-28 16:11:47
 * @Description: egg service 的装饰器，用于便捷注入绑定其他service，避免繁琐地通过文件目录索引使用其他service，且更方便重构
 */

import "reflect-metadata";
import { is_class } from "../lib/util";

/**
 * 用于装饰service或者controller类的装饰器，主要是执行constructor绑定上inject的属性
 * 在dinegg导出的service和controller中都已添加自动执行，可不需要此装饰器 重复绑定执行会报错。
 */
// export function service() {
// 	return function decorator<T extends { new (...args: any[]): {} }>(constructor: T) {
// 		class resultClass extends constructor {
// 			constructor(...args) {
// 				super(...args);
// 				bindInjectProperty.call(this);
// 			}
// 		}
// 		// 由于返回的class的name被改变，因此存一个静态属性
// 		resultClass[Symbol.for("#className")] = constructor.name;
// 		return resultClass;
// 	};
// }

/**
 *
 *service的属性注入装饰器 

 * @export
 * @param {(string | FunctionConstructor)} [className] 如果传入构造函数，需自行注意循环引用问题。
 * @return {*} 
 */
export function inject(...args) {
	return function decorator(prototype, propName) {
		const type = Reflect.getMetadata("design:type", prototype, propName);
		if (!type) return;
		if (!is_class(type)) return;
		defineProperty(prototype, propName, type, args);
	};
}

export const Inject = inject;

/**
 * 将属性对应的类实例化时机绑定在get访问时
 * - 属性被绑定在原型对象 Clz上，this上并没有。
 *
 *
 * @param {*} Clz 类的原型对象
 * @param {*} prop 装饰器修饰的属性name
 * @param {*} Value 装饰器携带的meteData 一定是一个类
 * @param {*} decoratorArgs 装饰器函数参数
 * @return {*}
 */
function defineProperty(Clz, prop, Value, decoratorArgs: any[]) {
	const symbolTempKey = Symbol("#this.prototype." + prop);
	Object.defineProperty(Clz, prop, {
		get() {
			if (this[symbolTempKey]) return this[symbolTempKey];
			// 根据传递进来的不同装饰器参数，传递给实例化的类
			if (decoratorArgs.length === 0) {
				// 如果装饰器未传递参数，则使用ctx或本实例对象this作为参数
				this[symbolTempKey] = new Value(this.ctx || this);
			} else if (decoratorArgs.length === 1 && decoratorArgs[0] instanceof Function) {
				// 有且只有一个参数为函数，则调用函数，将其返回值作为类实例化参数，返回值必须为数组。此函数接收类实例对象作为参数
				const args: any[] = decoratorArgs[0](this);
				if (!Array.isArray(args)) {
					throw new Error(`inject装饰器接收的参数为函数时，此函数必须返回一个数组！`);
				}
				this[symbolTempKey] = new Value(...args);
			} else {
				// 其他情况，多个参数或第一个参数不是函数，则直接传入类
				this[symbolTempKey] = new Value(...decoratorArgs);
			}
			return this[symbolTempKey];
		},
	});
}

// /**
//  * 提取封装，在装饰器，或者dinegg框架继承的service、controller中调用绑定inject的service
//  */
// export function bindInjectProperty(this: any) {
// 	if (!Array.isArray(this[propertyObjPropertyName])) {
// 		return;
// 	}
// 	// 从原型上绑定的要注入的列表中遍历注入app上绑定好的类
// 	this[propertyObjPropertyName].forEach(({ classFn, propName }) => {
// 		Object.defineProperty(this, propName, {
// 			get() {

// 				// 如果是类名称，则从app中提取类进实例化
// 				if (typeof className === "string") {
// 					const app = this.app._app || this.app;
// 					const ServiceClass = app._dineggServiceClasses[className];
// 					if (!ServiceClass) {
// 						throw new Error(`属性 ${propName} 要注入的类 ${className} 未找到`);
// 					}
// 					//   绑定初次调用才new但后续调用拿缓存，必须使用this.ctx因为ctx贯穿整个请求上下文
// 					if (!this.ctx[Symbol.for(className)]) {
// 						this.ctx[Symbol.for(className)] = new ServiceClass(this.ctx);
// 					}
// 					return this.ctx[Symbol.for(className)];
// 				} else if (className instanceof Function) {
// 					//   如果是 直接传入构造函数，则实例化之
// 					const ServiceClass = className;
// 					className = ServiceClass[Symbol.for("#className")] || ServiceClass.name;
// 					if (!this.ctx[Symbol.for(className)]) {
// 						this.ctx[Symbol.for(className)] = new ServiceClass(this.ctx);
// 					}
// 					return this.ctx[Symbol.for(className)];
// 				} else {
// 					throw new Error(`属性 ${propName} 要实例化的类 ${className} 不是类名或类`);
// 				}
// 			},
// 		});
// 	});
// }
